﻿using Microsoft.SharePoint.Administration;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Web.Script.Serialization;
using System.Windows.Forms;
using System.Web;

namespace Forze.SharePointTray
{
    public class Program : Form
    {
        private const string cServiceName = "SPTimerV4";
        private const string cSettingsMenu = "Settings";
        private const int cBalloonTimeout = 1; // seconds ( < 60 = refresh )
        private int interval = 0;
        public int Interval
        {
            get
            {
                if (interval != 0)
                {
                    return interval;
                }

                if (Properties.Settings.Default.Interval > 0 && Properties.Settings.Default.Interval < 60)
                {
                    interval = Properties.Settings.Default.Interval;
                }
                else
                {
                    interval = 1;
                }

                return interval;
            }
        }

        private NotifyIcon notifyIcon;
        private ContextMenuStrip menu;
        private Timer timer;
        private int ticks;
        private PictureBox pictureBox;
        private HttpWebRequest request;

        private bool didUpdate = false;
        private bool showGlobal = true;
        private bool force = false;
        private bool warmup = true;
        private bool iswarming = false;
        private IEnumerable<SPWebApplication> webApplications;
        private SPWebApplication selectedWebApplication;

        private Collection<SPWebApplication> selectedWebApplications
        {
            get
            {
                return new Collection<SPWebApplication> { selectedWebApplication };
            }
        }
        
        #region Initiation
        public Program()
        {
            menu = new ContextMenuStrip();
            menu.Items.Add(new ToolStripSeparator());
            menu.Items.Add("Exit", null, OnExit);

            notifyIcon = new NotifyIcon();
            notifyIcon.Icon = Properties.Resources.Unknown; // icons by http://p.yusukekamiyamane.com/

            notifyIcon.ContextMenuStrip = menu;
            notifyIcon.Visible = true;

            bool hasSharePoint = Init();
            ToolStripMenuItem settingsMenu = CreateSettingsMenu(hasSharePoint);
            if (hasSharePoint)
            {
                CreateWebApplicationMenu(settingsMenu);
            }
        }

        private bool Init()
        {
            if (SPFarm.Local == null)
            {
                MessageBox.Show("Can only be run on SharePoint (development) server", "Error");
                return false;
            }
            
            notifyIcon.Text = String.Format("Farm status: {0}", SPFarm.Local.Status.ToString());
            notifyIcon.Click += trayIcon_Click;

            if (SPFarm.Local.Status == SPObjectStatus.Online)
            {
                webApplications = SPFarm.Local.Services.GetValue<SPWebService>("").WebApplications;

                ticks = 0;
                timer = new Timer();
                timer.Interval = Interval * 1000;
                timer.Tick += timer_Tick;
                timer.Start();

                notifyIcon.Icon = Properties.Resources.Online;
                return true;
            }

            return false;
        }

        private ToolStripMenuItem CreateSettingsMenu(bool hasSharePoint)
        {
            menu.Items.Insert(0, new ToolStripSeparator());
            ToolStripMenuItem item = new ToolStripMenuItem(cSettingsMenu);
            item.Name = cSettingsMenu;
            menu.Items.Insert(0, item);

            var buttonReset = new ToolStripButton { Text = "Reset IIS" };
            buttonReset.Click+=buttonReset_Click;
            item.DropDownItems.Add(buttonReset);

            var buttonRecycle = new ToolStripButton { Text = "Recycle WebApplication" };
            buttonRecycle.Click += buttonRecycle_Click;
            item.DropDownItems.Add(buttonRecycle);

            if (hasSharePoint)
            {
                var buttonService = new ToolStripButton { Text = "Reset Timer" };
                buttonService.Image = Service.Status(cServiceName).Equals("Running") ? Properties.Resources.Online.ToBitmap() : Properties.Resources.Offline.ToBitmap();
                buttonService.Click += buttonService_Click;
                item.DropDownItems.Add(buttonService);
            }

            item.DropDownItems.Add(new ToolStripSeparator());

            var buttonGlobal = new ToolStripControlHost(new CheckBox { Text = "Global", Checked = true });
            buttonGlobal.Click += buttonGlobal_Click;
            buttonGlobal.BackColor = Color.Transparent;
            buttonGlobal.ToolTipText = "Show or hide globally deployed solutions";
            item.DropDownItems.Add(buttonGlobal);

            var buttonWarmup = new ToolStripControlHost(new CheckBox { Text = "Warmup", Checked = true });
            buttonWarmup.Click += buttonWarmup_Click;
            buttonWarmup.BackColor = Color.Transparent;
            buttonWarmup.ToolTipText = "Warmup WebApplication after deployments";
            item.DropDownItems.Add(buttonWarmup);

            FlowLayoutPanel pnl = new FlowLayoutPanel();
            Padding pad0 = new Padding(0);
            CheckBox checkBox = new CheckBox { Text = "Force", Checked = false, Margin = pad0 };
            checkBox.Click += buttonForce_Click;
            pnl.Controls.Add(checkBox);
            pictureBox = new PictureBox { Image = Properties.Resources.Force.ToBitmap(), Width = 24, Height = 24, SizeMode = PictureBoxSizeMode.StretchImage, Margin = pad0 };
            pictureBox.Hide();
            pnl.Controls.Add(pictureBox);

            var buttonForce = new ToolStripControlHost(pnl);
            buttonForce.BackColor = Color.Transparent;
            buttonForce.Margin = new Padding(0, -2, 0, 0);
            item.DropDownItems.Add(buttonForce);

            item.DropDownItems.Add(new ToolStripSeparator());

            return item;
        }

        private void CreateWebApplicationMenu(ToolStripMenuItem item)
        {
            if (webApplications != null)
            {
                foreach (SPWebApplication webApplication in webApplications)
                {
                    var button = new ToolStripControlHost(new RadioButton { Text = webApplication.Name, Width = 200 });
                    if (selectedWebApplication == null)
                    {
                        selectedWebApplication = webApplication;
                        ((RadioButton)button.Control).Checked = true;
                    }

                    button.Click += webApplication_Click;
                    button.BackColor = Color.Transparent;
                    item.DropDownItems.Add(button);
                }
            }
        }
        #endregion

        private void CollectSolutions()
        {
            if (iswarming == false)
            {
                notifyIcon.Icon = Properties.Resources.Online;
            }

            if (SPFarm.Local == null)
            {
                return;
            }

            try
            {
                var buttons = menu.Items.Cast<ToolStripItem>().Where(x => x.GetType() == typeof(ToolStripButton)).ToList();
                for (int i = 0; i < buttons.Count(); i++)
                {
                    menu.Items.Remove(buttons[i]);
                }

                IEnumerable<SPSolution> solutions = null;
                if (showGlobal)
                {
                    solutions = SPFarm.Local.Solutions;
                }
                else
                {
                    solutions = SPFarm.Local.Solutions.Where(x => x.ContainsWebApplicationResource);
                }

                if (solutions.Count() != 0)
                {
                    int i = 2; // 0 = SubMenu, 1 = Seperator
                    foreach (var solution in solutions)
                    {
                        Image status = CheckStatus(solution);
                        menu.Items.Insert(i, new ToolStripButton(solution.DisplayName, status, solution_Click));
                        i++;
                    }
                }
            }
            catch (Exception ex)
            {
#if DEBUG
                MessageBox.Show(ex.ToString());
#endif
            }
        }

        #region Warmup
        private void DoWarmup()
        {
            if (iswarming == false && warmup && selectedWebApplication != null)
            {
                iswarming = true;
                notifyIcon.Icon = Properties.Resources.Pizza;
                notifyIcon.Text = String.Format("Warming: {0}", selectedWebApplication.GetResponseUri(SPUrlZone.Default).AbsoluteUri);
                MakeAsyncRequest(selectedWebApplication.GetResponseUri(SPUrlZone.Default));
            }
            else
            {
                CollectSolutions();
            }
        }

//        private Task<string> MakeAsyncRequest(Uri url)
//        {
//            try
//            {
//                HttpWebRequest request = (HttpWebRequest)WebRequest.Create(new Uri(url, "_layouts/viewlsts.aspx"));
//                request.Method = WebRequestMethods.Http.Get;
//                request.Timeout = 20000;
//                request.Accept = "text/html, application/xhtml+xml";
//                request.Headers.Add("Accept-Encoding", "gzip, deflate");
//                request.UserAgent = "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)"; // IE9
//                request.Credentials = CredentialCache.DefaultNetworkCredentials;

//                Task<WebResponse> task = Task.Factory.FromAsync(request.BeginGetResponse, asyncResult => request.EndGetResponse(asyncResult), (object)null);

//                return task.ContinueWith(t => DoneWarmupRequest(request));
//            }
//            catch (Exception ex)
//            {
//#if DEBUG
//                MessageBox.Show(ex.ToString());
//#endif
//                notifyIcon.Icon = Properties.Resources.Unknown;
//                iswarming = false;
//                return null;
//            }
//        }

        private void MakeAsyncRequest(Uri url)
        {   
            // 3.5
            request = (HttpWebRequest)WebRequest.Create(url);
            request.Method = WebRequestMethods.Http.Get;
            request.Timeout = 20000;
            request.Accept = "text/html, application/xhtml+xml";
            request.Headers.Add("Accept-Encoding", "gzip, deflate");
            request.UserAgent = "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)"; // IE9
            request.Credentials = CredentialCache.DefaultNetworkCredentials;

            request.BeginGetResponse(new AsyncCallback(FinishWebRequest), null);
        }

        private void FinishWebRequest(IAsyncResult ar)
        {
            try
            {
                request.EndGetResponse(ar);
                DoneWarmupRequest(request);
            }
            catch (Exception ex)
            {
#if DEBUG
                MessageBox.Show(ex.ToString());
#endif
                notifyIcon.Icon = Properties.Resources.Unknown;
                notifyIcon.Text = String.Format("Farm status: {0}", SPFarm.Local.Status.ToString());
                iswarming = false;
            }
        }

        private string DoneWarmupRequest(HttpWebRequest parentRequest)
        {
            string returnValue = String.Empty;
            if (parentRequest.HaveResponse)
            {
                returnValue = "ok";
                notifyIcon.Icon = Properties.Resources.Online;

                if (Properties.Settings.Default.Chuck)
                {
                    HttpWebRequest request = (HttpWebRequest)WebRequest.Create("http://api.icndb.com/jokes/random");
                    try
                    {
                        WebResponse response = request.GetResponse();
                        using (Stream responseStream = response.GetResponseStream())
                        {
                            StreamReader reader = new StreamReader(responseStream, Encoding.UTF8);
                            returnValue = reader.ReadToEnd();
                            JavaScriptSerializer javaScriptSerializer = new JavaScriptSerializer();

                            Chuck norris = javaScriptSerializer.Deserialize<Chuck>(returnValue);

                            if (norris != null && norris.type.Equals("success"))
                            {
                                notifyIcon.ShowBalloonTip(cBalloonTimeout * 1000 * 10, "Ding!", HttpUtility.HtmlDecode(norris.value.joke), ToolTipIcon.None);
                            }
                            else
                            {
                                notifyIcon.ShowBalloonTip(cBalloonTimeout * 1000 * 5, "Ding!", "Ok", ToolTipIcon.None);
                            }
                        }
                    }
                    catch (WebException ex)
                    {
#if DEBUG
                        MessageBox.Show(ex.ToString());
#endif
                        notifyIcon.ShowBalloonTip(cBalloonTimeout * 1000 * 5, "Ding!", "Ok", ToolTipIcon.None);
                    }
                }
                else
                {
                    notifyIcon.ShowBalloonTip(cBalloonTimeout * 1000 * 5, "Ding!", "Ok", ToolTipIcon.None);
                }
            }
            else
            {
                returnValue = "nok";
                notifyIcon.Icon = Properties.Resources.Unknown;
            }

            iswarming = false;
            notifyIcon.Text = String.Format("Farm status: {0}", SPFarm.Local.Status.ToString());
            return returnValue;
        }
        #endregion

        private Image CheckStatus(SPSolution solution)
        {
            if (solution.JobExists)
            {
                return Properties.Resources.Working.ToBitmap();
            }
            else
            {
                if (!solution.LastOperationResult.ToString().Contains("Succeeded"))
                {
                    notifyIcon.Icon = Properties.Resources.Error;
                    return Properties.Resources.Error.ToBitmap();
                }
                else if (solution.Deployed && solution.ContainsWebApplicationResource == false) // Global
                {
                    return Properties.Resources.Online.ToBitmap();
                }
                else if (solution.Deployed && solution.ContainsWebApplicationResource && solution.DeployedWebApplications.Contains(selectedWebApplication))
                {
                    return Properties.Resources.Online.ToBitmap();
                }
                else if (selectedWebApplication == null)
                {
                    return Properties.Resources.Unknown.ToBitmap();
                }
                else
                {
                    return Properties.Resources.Offline.ToBitmap();
                }
            }
        }

        private static SPSolution FetchSolution(string solutionName)
        {
            SPSolution solution = SPFarm.Local.Solutions.SingleOrDefault(x => x.DisplayName == solutionName);

            if (solution == null)
            {
                DialogResult result = MessageBox.Show(String.Format("Solution {0} not found!", solutionName), "Error", MessageBoxButtons.RetryCancel, MessageBoxIcon.Error);
                if (result == DialogResult.Retry)
                {
                    return FetchSolution(solutionName);
                }
                else
                {
                    return null;
                }
            }

            return solution;
        }

        #region Events
        private void timer_Tick(object sender, EventArgs e)
        {
            try
            {
                IEnumerable<string> solutions =
                    from
                        solution in SPFarm.Local.Solutions
                    where
                        solution.JobExists
                    select
                        String.Format("{0} {1}", solution.Name, solution.JobStatus);

                if (solutions.Count() != 0)
                {
                    didUpdate = true;
                    ticks++;
                    //string time = TimeSpan.FromMilliseconds(ticks * Interval * 1000).ToString("mm\\:ss"); // 4.0
                    TimeSpan timeSpan = TimeSpan.FromMilliseconds(ticks * Interval * 1000);
                    string time = String.Format("{0:D2}:{1:D2}", timeSpan.Minutes, timeSpan.Seconds);
                    notifyIcon.ShowBalloonTip(cBalloonTimeout * 1000, time ?? "00:00", String.Join("\n", solutions.ToArray()) ?? String.Empty, ToolTipIcon.None);
                    notifyIcon.Icon = Properties.Resources.Working;
                }
                else
                {
                    if (didUpdate)
                    {
                        didUpdate = false;
                        DoWarmup();
                    }

                    ticks = 0;
                }
            }
            catch (Exception ex)
            {
#if DEBUG
                MessageBox.Show(ex.ToString());
#endif
            }
        }

        private void webApplication_Click(object sender, EventArgs e)
        {
            foreach (ToolStripControlHost item in ((ToolStripMenuItem)menu.Items[cSettingsMenu]).DropDownItems.Cast<object>().Where(o => o.GetType() == typeof(ToolStripControlHost)))
            {
                if (item.Control.GetType() == typeof(RadioButton))
                {
                    RadioButton rb = (RadioButton)item.Control;

                    if (rb.Checked == true && rb.Text != sender.ToString())
                    {
                        rb.Checked = false;
                    }

                    if (rb.Text == sender.ToString())
                    {
                        rb.Checked = true;
                    }
                }

                selectedWebApplication = webApplications.Single(x => x.Name == ((RadioButton)((ToolStripControlHost)sender).Control).Text);
                CollectSolutions();
            }
        }

        private void buttonReset_Click(object sender, EventArgs e)
        {
            if (iswarming == false)
            {
                IIS.Reset();
                DoWarmup();
            }
        }

        private void buttonRecycle_Click(object sender, EventArgs e)
        {
            if (iswarming == false)
            {
                if (selectedWebApplication != null)
                {
                    if (force)
                    {
                        IIS.ResetPool(selectedWebApplication.Name); // hard
                    }
                    else
                    {
                        IIS.RecyclePool(selectedWebApplication.Name); // soft
                    }

                    DoWarmup();
                }
            }
        }

        private void buttonService_Click(object sender, EventArgs e)
        {
            Service.Restart(cServiceName, 1000);
            ((ToolStripButton)sender).Image = Properties.Resources.Online.ToBitmap();
        }

        private void buttonGlobal_Click(object sender, EventArgs e)
        {
            showGlobal = ((CheckBox)((ToolStripControlHost)sender).Control).Checked;
            CollectSolutions();
        }

        private void buttonWarmup_Click(object sender, EventArgs e)
        {
            warmup = ((CheckBox)((ToolStripControlHost)sender).Control).Checked;
        }

        private void buttonForce_Click(object sender, EventArgs e)
        {
            force = ((CheckBox)sender).Checked;
            if (force)
            {
                pictureBox.Show();
            }
            else
            {
                pictureBox.Hide();
            }

            CollectSolutions();
        }

        private void trayIcon_Click(object sender, EventArgs e)
        {
            CollectSolutions();
        }

        private void solution_Click(object sender, EventArgs e)
        {
            SPSolution solution = FetchSolution(sender.ToString());

            if (solution == null)
            {
                return;
            }

            // WebApplication
            if (solution.ContainsWebApplicationResource)
            {
                if (solution.Deployed && solution.DeployedWebApplications.Contains(selectedWebApplication))
                {
                    solution.Retract(DateTime.Now, selectedWebApplications);
                }
                else
                {
                    solution.Deploy(DateTime.Now, true, selectedWebApplications, force);
                }
            }
            else
            {
                // global
                if (solution.Deployed)
                {
                    solution.Retract(DateTime.Now);
                }
                else
                {
                    solution.Deploy(DateTime.Now, true, force);
                }
            }

            //if (solution.Deployed)
            //{
            //    if (solution.ContainsWebApplicationResource && solution.DeployedWebApplications.Contains(selectedWebApplication))
            //    {
            //        solution.Retract(DateTime.Now, selectedWebApplications);
            //    }
            //    else if (solution.ContainsWebApplicationResource && !solution.DeployedWebApplications.Contains(selectedWebApplication))
            //    {
            //        MessageBox.Show(String.Format("Solution {0} is deployed on other WebApplications:", solution.DisplayName, String.Join(" and ", solution.DeployedWebApplications.Select(x => x.DisplayName).ToArray())), "Cannot retract", MessageBoxButtons.OK, MessageBoxIcon.Warning);
            //    }
            //    else
            //    {
            //        solution.Retract(DateTime.Now);
            //    }
            //}
            //else
            //{
            //    if (solution.ContainsWebApplicationResource)
            //    {
            //        solution.Deploy(DateTime.Now, true, selectedWebApplications, force);
            //    }
            //    else
            //    {
            //        solution.Deploy(DateTime.Now, true, force);
            //    }
            //}
        }
        #endregion

        #region App stuff
        [STAThread]
        public static void Main()
        {
            //http://www.codeproject.com/Articles/20775/Notify-Icon-in-System-Tray-with-Context-Menu-Using
            Application.Run(new Program());
        }

        protected override void OnLoad(EventArgs e)
        {
            Visible = false;
            ShowInTaskbar = false;

            base.OnLoad(e);
        }

        private void OnExit(object sender, EventArgs e)
        {
            Application.Exit();
        }

        protected override void Dispose(bool isDisposing)
        {
            if (isDisposing)
            {
                notifyIcon.Dispose();
            }

            base.Dispose(isDisposing);
        }
        #endregion

    }
}